package com.hontech.bvapp2.device

import com.hontech.bvapp2.app.AppContext
import com.hontech.bvapp2.utils.ConnTask
import com.hontech.bvapp2.utils.TimeoutException
import com.hontech.bvapp2.utils.await
import com.hontech.bvapp2.utils.log
import com.hontech.bvapp2.utils.runIO
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import serial.SerialPort
import serial.codec.CodecType
import serial.codec.u8
import java.io.IOException
import java.util.concurrent.ConcurrentHashMap

object DeviceConn {

    private const val BAUD_RATE = 115200
    private var port: SerialPort? = null
    private val router = ConcurrentHashMap<Int, CompletableDeferred<Frame>>()
    private var globalSeq = 0

    // Future, Promise
    @Synchronized
    private fun createSeq(): Int {
        val seq = globalSeq
        globalSeq += 1
        if (globalSeq > 255) {
            globalSeq = 0
        }
        return seq
    }

    suspend fun open(name: String) {
        val port = SerialPort.open(AppContext, name, BAUD_RATE)
        ReadTask(port, router).start()
        runIO { this.port = port }
    }

    fun isOpen() = this.port != null

    suspend fun close() {
        val port = this.port ?: return
        runIO {
            port.close()
            this.port = null
        }
    }

    private suspend fun write(buf: ByteArray) {
        val port = this.port ?: throw IllegalStateException("串口没有打开")
        runIO { port.write(buf) }
    }

    private suspend fun exec(seq: Int, dst: Int, retTy: Int, buf: ByteArray, recount: Int = 3, timeout: Long = 500): Frame {
        val id = (dst shl 16) or (retTy shl 8) or (seq)
        val fut = CompletableDeferred<Frame>()
        router[id] = fut

        for (i in 0 until recount) {
            write(buf)
            try {
                return fut.await(timeout, "发送超时")
            } catch (e: TimeoutException) {
                e.printStackTrace()
            }
        }
        throw IOException("发送3次失败 目标没有回应")
    }

    suspend fun ping(dst: Int): Frame {
        val seq = createSeq()
        val buf = Proto.makeEmpty(Ty.PING, seq, Address.MY, dst)
        return exec(seq, dst, Ty.PONG, buf)
    }

    suspend fun simpleReq(dst: Int, cmd: Int, vararg args: CodecType): Frame {
        val seq = createSeq()
        val buf = Proto.make(Ty.SIMPLE_REQ, seq, Address.MY, dst, cmd.u8(), *args)
        return exec(seq, dst, Ty.SIMPLE_RES, buf)
    }

    suspend fun simpleReqOta(dst: Int, cmd: Int, vararg args: CodecType): Frame {
        val seq = createSeq()
        val buf = Proto.make(Ty.SIMPLE_REQ, seq, Address.MY, dst, cmd.u8(), *args)
        return exec(seq, dst, Ty.SIMPLE_RES, buf, 1, 1000)
    }

    private suspend fun poll(seq: Int, dst: Int): Frame {
        val buf = Proto.makeEmpty(Ty.POLL, seq, Address.MY, dst)
        return exec(seq, dst, Ty.POLL_ACK, buf)
    }

    private suspend fun connect(seq: Int, dst: Int): Frame {
        val buf = Proto.makeEmpty(Ty.CONNECT, seq, Address.MY, dst)
        return exec(seq, dst, Ty.CONNECT_ACK, buf)
    }

    suspend fun req(dst: Int, cmd: Int, vararg args: CodecType): Frame {
        val seq = createSeq()

        connect(seq, dst)

        val resFut = CompletableDeferred<Frame>()
        val id = (dst shl 16) or (Ty.RES shl 8) or (seq)
        router[id] = resFut

        val reqBuf = Proto.make(Ty.REQ, seq, Address.MY, dst, cmd.u8(), *args)
        exec(seq, dst, Ty.REQ_ACK, reqBuf)

        var finishFlag = false

        while (true) {
            try {
                return resFut.await(3000, "等待返回超时")
            } catch (e: TimeoutException) {
                e.printStackTrace()
                if (finishFlag) {
                    throw IllegalStateException("任务已经完成 但是没有返回")
                }
            }
            val pollAck = poll(seq, dst)
            finishFlag = 0 != pollAck.pollResult()
        }
    }

}

class ReadTask(
    private val port: SerialPort,
    private val router: Map<Int, CompletableDeferred<Frame>>
) : Thread() {

    override fun run() {

        while (true) {

            try {
                readOnce()
            } catch (e: Exception) {
                e.printStackTrace()
                break
            }
        }
        log("read task exit")
    }

    private fun readOnce() {
        val buf = port.read()
        val frame = Frame(buf)
        //log("read:$frame")

        when (frame.ty) {
            Ty.LOG -> {
                Back.onLog(frame)
                return
            }
            Ty.NOTIFY -> {
                Back.onNotify(frame)
                return
            }
            Ty.PONG, Ty.CONNECT_ACK, Ty.POLL_ACK -> {}
            Ty.RES -> {
                val resAck = Proto.makeEmpty(Ty.RES_ACK, frame.seq, frame.dst, frame.src)
                ConnTask.post { port.write(resAck) }
            }
        }
        val id = frame.id()
        val item = router[id]
        item?.complete(frame)
    }
}






